use std::sync::Arc;

use axum::{
    response::IntoResponse,
    routing::{get, post},
    Extension, Json, Router,
};
use validator::Validate;

use crate::{
    error::HttpError,
    middleware::{jwt_auth, UserToken},
    models::{FilterUser, LoginResult, LoginUser, RegisterUser},
    utils::{compare_passwords, encode_token, hash_password},
    AppState,
};

type R<T> = Result<Json<T>, HttpError>;

pub fn create_router() -> Router {
    let router = Router::new()
        .route("/", post(register))
        .route("/login", post(login))
        .route(
            "/{user_id}",
            get(welcome).layer(axum::middleware::from_fn(jwt_auth)),
        );

    Router::new().nest("/users", router)
}

pub async fn register(
    Extension(state): Extension<Arc<AppState>>,
    Json(payload): Json<RegisterUser>,
) -> R<FilterUser> {
    let hashed_password =
        hash_password(&payload.password).map_err(|e| HttpError::server_error(e.to_string()))?;

    let result = state.db_client.save_user(&payload.email, &hashed_password).await;

    match result {
        Ok(user) => Ok(Json(FilterUser::from(user))),
        Err(e) => Err(HttpError::server_error(e.to_string())),
    }
}

pub async fn login(
    Extension(state): Extension<Arc<AppState>>,
    Json(payload): Json<LoginUser>,
) -> Result<impl IntoResponse, HttpError> {
    payload
        .validate()
        .map_err(|e| HttpError::bad_request(e.to_string()))?;

    let result = state
        .db_client
        .get_user_by_email(&payload.email)
        .await
        .map_err(|e| HttpError::server_error(e.to_string()))?;

    let user = result.ok_or(HttpError::bad_request("Wrong credentials"))?;

    let password_matched = compare_passwords(&payload.password, &user.password)
        .map_err(|_| HttpError::bad_request("Wrong credentials"))?;

    if password_matched {
        let token = encode_token(
            &user.id,
            state.config.jwt_secret_key.as_bytes(),
            state.config.jwt_max_age,
        )
        .map_err(|e| HttpError::server_error(e.to_string()))?;

        Ok(Json(LoginResult { token }))
    } else {
        Err(HttpError::bad_request("Wrong credentials"))
    }
}

pub async fn welcome(Extension(token): Extension<UserToken>) -> impl IntoResponse {
    return format!("welcome, {}", token.user.id);
}
